import { ref, shallowRef, type Ref } from 'vue'

export interface TocCounts {
  cards: number
  images: number
  links: number
}

export interface TocNode {
  id: string
  level: 1 | 2 | 3
  text: string
  el: HTMLElement
  children: TocNode[]
  counts: TocCounts
}

interface UseToc {
  nodes: Ref<TocNode[]>
  activeId: Ref<string | null>
  init: (rootEl: HTMLElement) => void
  destroy: () => void
  refresh: () => void
  navigateTo: (id: string) => void
}

export function useToc(): UseToc {
  const nodes = ref<TocNode[]>([])
  const activeId = ref<string | null>(null)
  const rootRef = shallowRef<HTMLElement | null>(null)

  let mo: MutationObserver | null = null
  let io: IntersectionObserver | null = null
  let buildTimer: number | null = null
  let building = false

  function uid() {
    return Math.random().toString(36).slice(2, 10)
  }

  function scheduleBuild() {
    if (buildTimer) {
      window.clearTimeout(buildTimer)
    }
    buildTimer = window.setTimeout(build, 200)
  }

  function init(rootEl: HTMLElement) {
    rootRef.value = rootEl
    // Listen to DOM mutations for realtime refresh
    mo = new MutationObserver(() => scheduleBuild())
    mo.observe(rootEl, {
      childList: true,
      subtree: true,
      characterData: true
    })
    build()
    // 再次调度一次，确保初始渲染后的节点也被解析
    scheduleBuild()
  }

  function destroy() {
    if (mo) mo.disconnect()
    if (io) io.disconnect()
    mo = null
    io = null
    rootRef.value = null
    nodes.value = []
    activeId.value = null
    if (buildTimer) window.clearTimeout(buildTimer)
    buildTimer = null
  }

  function build() {
    const root = rootRef.value
    if (!root) return
    const rootEl = root as HTMLElement
    building = true

    // Collect headings inside root
    const allHeads = Array.from(root.querySelectorAll<HTMLElement>('h1, h2, h3'))
    // Ensure stable ids
    allHeads.forEach((h) => {
      const level = Number(h.tagName.substring(1)) as 1 | 2 | 3
      if (level < 1 || level > 3) return
      if (!h.dataset.tocId) {
        const id = uid()
        h.dataset.tocId = id
      }
      const idVal = h.dataset.tocId!
      if (h.id !== `toc-${idVal}`) h.id = `toc-${idVal}`
    })

    // Build hierarchical tree using stack
    const roots: TocNode[] = []
    const stack: TocNode[] = []

    function mkCountsForSection(startEl: HTMLElement, nextEl: HTMLElement | null): TocCounts {
      const range = document.createRange()
      range.setStartAfter(startEl)
      if (nextEl) {
        range.setEndBefore(nextEl)
      } else {
        // to the end of root
        const last = rootEl.lastChild
        if (last) range.setEndAfter(last)
      }
      const frag = range.cloneContents()
      const cards = frag.querySelectorAll('.rte-card').length
      const images = frag.querySelectorAll('img').length
      const links = frag.querySelectorAll('a[href]').length
      return { cards, images, links }
    }

    const nodesLinear: TocNode[] = allHeads.map((h) => {
      const level = Number(h.tagName.substring(1)) as 1 | 2 | 3
      const id = h.dataset.tocId || uid()
      return {
        id,
        level,
        text: (h.textContent || '').trim(),
        el: h,
        children: [],
        counts: { cards: 0, images: 0, links: 0 }
      }
    })

    // Compute counts by section boundaries
    nodesLinear.forEach((node, idx) => {
      const nextSameOrHigher = nodesLinear.slice(idx + 1).find((n) => n.level <= node.level)
      node.counts = mkCountsForSection(node.el, nextSameOrHigher?.el || null)
    })

    // Build nested structure
    nodesLinear.forEach((node) => {
      while (stack.length && stack[stack.length - 1].level >= node.level) {
        stack.pop()
      }
      if (stack.length === 0) {
        roots.push(node)
      } else {
        stack[stack.length - 1].children.push(node)
      }
      stack.push(node)
    })

    // Update active observer
    if (io) io.disconnect()
    io = new IntersectionObserver(
      (entries) => {
        // pick the entry closest to top (largest intersection ratio or positive isIntersecting)
        const visible = entries
          .filter((e) => e.isIntersecting)
          .sort((a, b) => b.intersectionRatio - a.intersectionRatio)
        if (visible.length) {
          const el = visible[0].target as HTMLElement
          const id = el.dataset.tocId || ''
          if (id) activeId.value = id
        }
      },
      { root: null, rootMargin: '-20% 0px -60% 0px', threshold: [0, 0.1, 0.5, 1] }
    )
    nodesLinear.forEach((n) => io!.observe(n.el))

    nodes.value = roots
    building = false
  }

  function refresh() {
    if (building) return
    scheduleBuild()
  }

  function navigateTo(id: string) {
    const root = rootRef.value
    if (!root) return
    const heading = root.querySelector<HTMLElement>(`[data-toc-id="${id}"]`) || root.querySelector<HTMLElement>(`#toc-${id}`)
    if (heading) {
      heading.scrollIntoView({ behavior: 'smooth', block: 'start' })
    }
  }

  return { nodes, activeId, init, destroy, refresh, navigateTo }
}

export default useToc
